/*
 *  file MigBSPBLB.C
 *  Author: Roberto Gomes
 *  Date Created: July 3rd, 2013
 *  E-mail: rqg.gomes@gmail.com
 *
 */
#include "MigBSPBLB.h"
#define PF
CreateLBFunc_Def(MigBSPBLB, "It combines computation, communication and migration cost")

MigBSPBLB::MigBSPBLB(const CkLBOptions &opt): CentralLB(opt)
{

	proc_maxload = 0;
	j_max = 0;
	i_max = 0;
	lbname = "MigBSPBLB";
	n_procs = 0; //armazena o número de processadores
	n_tasks = 0; //armazena o número de tarefas
	D = 0.5;

	alpha = _lb_args.alpha();//Lê o parâmetro da linha de comando
	_alpha_ref = alpha;

	if((_lb_args.beeta() < 1) && (_lb_args.beeta() > 0))
		D = _lb_args.beeta();//Lê o parâmetro da linha de comando

	if( CkMyPe() == 0)
	{
		CkPrintf("D=%f\n",D);
		CkPrintf("MigBSPBLB created\n");
		CkPrintf("Gerando a Matriz de Potencial de Migração\n");
	}
	PM = new vector< vector<double>* >();
	PMListMtx = new vector < vector<pm_cadidate>*>();
	PMmax = new vector <pm_cadidate>();
	PMList = new vector <pm_cadidate> ();
	B = new vector<vector<long> *>();
	//	CompStatics = new vector <MigBSPBLBProcStatiscs*>();
}

CmiBool MigBSPBLB::QueryBalanceNow(int _step)
{
	return CmiTrue;
}
///////////////////////////////////////////
void MigBSPBLB::work(LDStats *stats)
{
	//	int i;
	/** ============== INITIALIZATION ================ */
	ProcArray *parr = new ProcArray(stats);
	ObjGraph *ogr = new ObjGraph(stats);
	ObjGraph *tmp = new ObjGraph(stats);//usado para manipulação
	/** ================= STRATEGY =================== */
	ProcFullInfo *pfi = new ProcFullInfo(stats);

	if(MigBSPComputePM(pfi, parr , ogr , stats))
	{
		MigBSPMigrate(pfi,ogr,tmp);
		ogr->convertDecisions(stats);
	}
	/** ================== CLEANUP =================== */
	delete parr;
	delete pfi;
	delete ogr;
	delete tmp;
}
/* ****************************************************
 * @return true if unbalanced state was detected
 * ****************************************************/
bool MigBSPBLB::MigBSPComputePM(ProcFullInfo *pfi, ProcArray* procs,ObjGraph *objectgraphs, LDStats *sta)
{
	int i,j,k;
	pm_cadidate p;
	double mean_load_min, mean_load_max;
	double total_load = 0;
	double max_load = 0;//mantém o valor máximo de tempo de execução
	double min_load = 1e30;//mantém o valor mínimo de tempo de execução
	int procpar = 0;

	mean_load = 0;

	if(--alpha >= 0)
		return	false;

	n_procs = pfi->getNumProcs();
	n_tasks = objectgraphs->vertices.size();
	i_max = 0;
	j_max = 0;
	proc_maxload = 0;

	p.i=0;
	p.j=0;
	p.pm_val=0;

	if ( n_procs != PM->size())//ajusta o tamanho da matriz PM(x,y)
	{
		int count = PM->size();
		for ( i = count; i < n_procs; i++)
		{
			B->push_back(new vector<long>());
			PMmax->push_back(p);
			//PMListMtx->push_back(new vector<pm_cadidate>());
			PM->push_back(new vector<double>);//
			for( j = 0 ; j < n_tasks; j++)
			{
				B->at(i)->push_back(0);
				PM->at(i)->push_back(0);//
				//PMListMtx->at(i)->push_back(p);
			}
		}
	}
	/*******************************************************
	 *    It shows the normalized speeds of processors     *
	 *******************************************************/
	if (_lb_args.debug() > 0)
		for(int j = 0;j < n_procs;j++)
		{
			ProcStats pi = sta->procs[j];
			CkPrintf("P[%d] - %lf Pload=%f\n",j,
					pi.pe_speed,pfi->getLoad(j));
		}
	
	/******************************************************
	 *   It tries discover if the process is unbalaced    *
	 ******************************************************/

	if (_lb_args.debug() > 1)
		for( i = 0; i < n_tasks; i++)
		{
			CkPrintf("\"vertice\", \"%d\",\"%d\"\n",i, objectgraphs->vertices[i].getCurrPe());
		}

	for( i = 0; i < n_procs; i++)
	{
		{
			procpar++;
			if(pfi->getTime(i) > max_load )
			{
				max_load = pfi->getTime(i); //acha o mais lento
				Pjslower=i;
			}

			if(pfi->getTime(i) < min_load)
			{
				min_load = pfi->getTime(i); //hold major task load}
				Pjfaster=i;
			}
			total_load += pfi->getTime(i);
		}
	}
	//CkPrintf("Pfaster= %d\nPslower = %d\n",Pjfaster,Pjslower);
	mean_load = total_load / procpar;//mean load among tasks
	//Se o max_load e min_load são valores dentro da margem
	if((min_load > mean_load*(1 - D)) && (max_load < mean_load *(1 + D)))
	{
		if (_lb_args.debug() > 0)
			CkPrintf("Balanceado!\n");
		//CompStatics->clear();
		_alpha_ref *= 2;
		//_alpha_ref ++;
		alpha = _alpha_ref ;
		//CkPrintf("mean_load=%f min_load=%f max_load=%f\n", mean_load,min_load,max_load);
		return false;//se balanceado sai da tarefa
	}else
	{
		if (_lb_args.debug() > 0)
			CkPrintf("Desbalanceado!\n");
		alpha = 1 ;
		_alpha_ref=1;
	}
	/**********************************
	 *    clean previus information   *
	 **********************************/
	for ( i = 0; i < n_procs; i++)
	{
		for ( j = 0; j < n_tasks; j++)
		{
			B->at(i)->at(j) = 0;
		}
	}

	for ( i = 0; i < n_tasks; i++)
	{
		for ( j = 0 ; j < objectgraphs->vertices[i].recvFromList.size(); j++)
		{
			k = objectgraphs->vertices[objectgraphs->vertices[i].recvFromList[j].getNeighborId()].getCurrPe();
			B->at(k)->at(i) = B->at(k)->at(i) +
		objectgraphs->vertices[i].recvFromList[j].getNumBytes();//tempo de execução
			if (_lb_args.debug() == 10)
			{
				CkPrintf("V[%d]Recebeu de %d remoto %ld bytes!\n",
						objectgraphs->vertices[i].getVertexId(),k,B->at(k)->at(i));
			}
		}
	}

	if (_lb_args.debug() == 10)
	{
		for ( j = 0; j < n_procs; j++)
		{
			for ( i = 0; i < n_tasks; i++)
			{
				CkPrintf("B->at(%d)->at(%d) = %ld;\n",j,i,B->at(j)->at(i));
			}
		}
	}
	/***********************************
	 *Calcula o potencial de migração  *
	 *Complexidade O(n_procs x n_tasks)*
	 ***********************************/
	mean_load_min = mean_load*( 1 - D);
	mean_load_max = mean_load*( 1 + D);
	
	i=0;
	if((pfi->getTime(Pjslower) > mean_load_max) ||
			(pfi->getTime(Pjfaster) < mean_load_min))
	{
		for ( j = 0; j < n_procs; j++)
		{
			if (_lb_args.debug() == 3)
				CkPrintf("PM%d ",j);

			for ( i = 0; i < n_tasks; i++)
			{
				ProcInfo *pi = &procs->procs[j];
				ProcStats *ps = &sta->procs[j];
				Vertex task = objectgraphs->vertices[i];
				if (_lb_args.debug() == 6)
					CkPrintf("i = %d  id = %d nlist=%d\n",i,
							task.getVertexId(),objectgraphs->vertices.at(i).getVertexId());
				if (_lb_args.debug() == 6)
					CkPrintf("B->at(%d)->at(%d) = %ld;\n",j,i,B->at(j)->at(i));
				PM->at(j)->at(i) =
				Comp(pfi,j,&task)+
				Comm(pi,&task,objectgraphs)-
				Mem(&task);
				p.i = i;
				p.j = j;
				p.pm_val= PM->at(j)->at(i) > 0 ? PM->at(j)->at(i):0;

				if (_lb_args.debug() == 3)
					CkPrintf(",\"%.2lf\"",p.pm_val);
				PM->at(j)->at(i) = p.pm_val;

			}
			if (_lb_args.debug() == 3)
				CkPrintf("\n");
		}
	}
	return true;
}


double MigBSPBLB::Comp(ProcFullInfo* pfi,int j, Vertex* tsk)
{
	double res = 0;
	double res2 = 0;
	double CTP = 0;
	double PI = 0;

	if (_lb_args.debug() > 3)
		CkPrintf("::Comp\n");

	double erro = mean_load - pfi->getTime(j);
	double erro2 = mean_load - pfi->getTime(tsk->getCurrentPe());

	if (erro < 0 || erro2 > 0)
		return 0.0;

	CTP = tsk->getCompLoad();
	PI = CTP * (pfi->getSpeed(tsk->getCurrentPe())*pfi->getLoad(tsk->getCurrentPe()));
	res= (PI/ (pfi->getSpeed(j) * (1 - pfi->getLoad(j)))) + pfi->getTime(j) ; //novo tempo em segundos

	if (_lb_args.debug() > 3)
		CkPrintf("Comp: V[%d]P[%d] prev %lf "
				"PI=%lf ptime=%lf "
				"sporigem %lf "
				"spdestino %lf "
				"Ocupporigem=%lf "
				"Ocuppdest=%lf "
				"Comp=%lf\n",
		tsk->getVertexId(), j,PI /( pfi->getSpeed(j) * (1 - pfi->getLoad(j))) + pfi->getTime(j),
		PI,pfi->getTime(tsk->getCurrentPe()),
		pfi->getSpeed(tsk->getCurrPe()),
		pfi->getSpeed(j),
		pfi->getLoad(tsk->getCurrPe()),
		pfi->getLoad(j),
		pfi->getTime(tsk->getCurrentPe()) - res);
	return pfi->getTime(tsk->getCurrentPe()) - res;
}

double MigBSPBLB::Comm(ProcInfo *ps, Vertex *t, ObjGraph* obj)
{
	double res;
	res = ((double)B->at(ps->getProcId())->at(t->getVertexId())) * K_TRANSFER_BYTES;

	if (_lb_args.debug() > 3)
		CkPrintf("Comm: V[%d]P[%d]= %ld Comm = %lf\n",
		t->getVertexId(),ps->getProcId(), B->at(ps->getProcId())->at(t->getVertexId()),	res);

	return res;
}

double MigBSPBLB::Mem(Vertex *t)
{
	float res = t->getpupSize() * K_TRANSFER_BYTES;
	if (_lb_args.debug() > 3)
		CkPrintf("Mem: V[%d] Size: %lu Mem = %lf\n",t->getVertexId(),t->getpupSize(),(double)t->getpupSize() * (double)K_TRANSFER_BYTES);
	return res;
}

void MigBSPBLB::MigBSPMigrate(ProcFullInfo* pfi, ObjGraph *objectgraphs,ObjGraph *temp)
{
	int n_trans,j,i,last_i,k,last_j,
	ppl = 0,
	max_task_time = 0,
	max_pm_pom = 0,
	max_mem = 0;
	double pf,migcost=0;
	pm_cadidate p;
	double current_worst_schedule, current_max_step_time;
#ifdef PF
	/****************************************************
	 * Modelo do algoritmo proposto na dissertação  (pf)*
	 ****************************************************/
	pf = 0;
	current_max_step_time = Calc_pf(objectgraphs,(double)0.0,pfi);

	while(current_max_step_time >= pf)
	{
	/*****************************************************
	 *Encontra o maior PM de ppl
	 ******************************************************/
		p.i = 0;
		p.j = 0;
		p.pm_val = 0;

		PMmax->at(ppl) = p;
		for(j = 0;j < n_procs; j++)
			for(i = 0;i < n_tasks; i++)
			{
				if(PMmax->at(ppl).pm_val < PM->at(j)->at(i))
				{
					p.j = j; p.i = i;
					p.o = objectgraphs->vertices.at(p.i).getCurrPe();
					p.pm_val = PM->at(j)->at(i);
					PMmax->at(ppl) = p;
				}
			}

		//CkPrintf("----\n");

		if(PMmax->at(ppl).pm_val > 0)
		{

			/***********************************************************
			 * aplica as simulaçoes de valores de carga dos processos
			 ***********************************************************/
			double CTP = 0;
			double PI = 0;
			double load_proc_i = pfi->getLoad(temp->vertices.at(PMmax->at(ppl).i).getCurrPe());
			double speed_proc_i = pfi->getSpeed(temp->vertices.at(PMmax->at(ppl).i).getCurrPe()) * (1-load_proc_i);
			double load_proc_j = pfi->getLoad(PMmax->at(ppl).j);
			double speed_proc_j = pfi->getSpeed(PMmax->at(ppl).j) * (1-load_proc_j);

			CTP = temp->vertices.at(PMmax->at(ppl).i).getCompLoad();
			if (_lb_args.debug() > 4)
				CkPrintf("V[%d] dura %lf no Proc[%d]\n",
						PMmax->at(ppl).i,
						CTP,
						temp->vertices.at(PMmax->at(ppl).i).getCurrPe());
			PI = CTP*speed_proc_i;
			if (_lb_args.debug() > 4)
				CkPrintf("V[%d] executa %lf no Proc[%d]\n",
						PMmax->at(ppl).i,
						PI,
						temp->vertices.at(PMmax->at(ppl).i).getCurrPe());

			temp->vertices.at(PMmax->at(ppl).i).setCompLoad(PI/speed_proc_j);
			if (_lb_args.debug() > 4)
				CkPrintf("V[%d] durará %lf no Proc[%d]\n",
						PMmax->at(ppl).i,
						temp->vertices.at(PMmax->at(ppl).i).getCompLoad(),
						PMmax->at(ppl).j);
			if (_lb_args.debug() > 4)
				CkPrintf("Porig[%d] carga atual é de %lf Pdest[%d] é de %lf\n ",
						PMmax->at(ppl).o,pfi->getLoad(PMmax->at(ppl).o),
						PMmax->at(ppl).j,pfi->getLoad(PMmax->at(ppl).j));

			pfi->setIdle(PMmax->at(ppl).j,
					pfi->getIdle(PMmax->at(ppl).j)-PI/speed_proc_j);

			pfi->setLoad(PMmax->at(ppl).j,
					(pfi->getWall(PMmax->at(ppl).j)-pfi->getIdle(PMmax->at(ppl).j))
					/pfi->getWall(PMmax->at(ppl).j));

			pfi->setTime(PMmax->at(ppl).j,pfi->getWall(PMmax->at(ppl).j) - pfi->getIdle(PMmax->at(ppl).j));

			pfi->setIdle(PMmax->at(ppl).o,
					pfi->getIdle(PMmax->at(ppl).o) + PI/speed_proc_i);

			pfi->setTime(PMmax->at(ppl).o, pfi->getWall(PMmax->at(ppl).o) - pfi->getIdle(PMmax->at(ppl).o));

			pfi->setLoad(PMmax->at(ppl).o,
					(pfi->getWall(PMmax->at(ppl).o)-pfi->getIdle(PMmax->at(ppl).o))
					/pfi->getWall(PMmax->at(ppl).o));


			if (_lb_args.debug() > 5)
				CkPrintf("Porig[%d] carga futura é de %lf Pdest[%d] será de %lf\n",
						PMmax->at(ppl).o,pfi->getLoad(PMmax->at(ppl).o),
						PMmax->at(ppl).j,pfi->getLoad(PMmax->at(ppl).j));
			/**************************************************************
			 * aplica as simulaçoes de valores de comunicação dos processos
			 **************************************************************/
			for ( i = 0; i < n_tasks; i++)
			{
				for ( j = 0 ; j < temp->vertices[i].recvFromList.size(); j++)
				{
					k = temp->vertices[temp->vertices[i].recvFromList[j].getNeighborId()].getCurrPe();
					B->at(k)->at(i) = B->at(k)->at(i) +
							temp->vertices[i].recvFromList[j].getNumBytes();//tempo de execução
					if (_lb_args.debug() > 4)
					{
						CkPrintf("V[%d]Recebeu de %d remoto %ld bytes!\n",
							temp->vertices[i].getVertexId(),k,B->at(k)->at(i));
					}
				}
			}
			//limpa todos os PM que envolvem o processo movido
			for(j = 0; j < n_procs; j++)
			{
				PM->at(j)->at(PMmax->at(ppl).i) = 0;
			}
			//esta linha é fake... usada para fazer um posicionamento para previsão
			temp->vertices.at(PMmax->at(ppl).i).setCurrPe(PMmax->at(ppl).j);
			pf = Calc_pf(temp,migcost,pfi);

			if(pf<= current_max_step_time)
			{
				//CkPrintf("É bom!Migra\n");
				current_max_step_time = pf;
				objectgraphs->vertices.at(PMmax->at(ppl).i).setNewPe(PMmax->at(ppl).j);
				migcost += objectgraphs->vertices.at(PMmax->at(ppl).i).getpupSize()*K_TRANSFER_BYTES;
			}else
			{
				if (_lb_args.debug() > 1)
					CkPrintf("É ruim!Não migra\n");
			}

		}else
		{
			if (_lb_args.debug() > 1)
				CkPrintf("Todos PM = 0");
			pf = 1e10;
		}

		if (_lb_args.debug() > 1)
			CkPrintf("current= %lf  pf =%lf\n",current_max_step_time,pf);

	}
#endif
}

double MigBSPBLB::Calc_pf(ObjGraph* s, double migcost,ProcFullInfo* pfi)
{
	double pf;
	int i , j;
	int max_task_time;
	int max_com_i;
	int max_com_j;
	int proc_max;
	int max_mem;
	proc_max = 0;
	max_mem = 0;
	max_com_i = 0;
	max_com_j = 0;
	max_task_time = 0;

	/******************************************
	 * Encontra a tarefa mais lenta
	 *******************************************/
	for(i = 0; i < n_tasks; i++)
	{
		if(s->vertices.at(max_task_time).getCompLoad() < s->vertices.at(i).getCompLoad())
			max_task_time = i;
	}

	/******************************************
	 * Encontra a tarefa mais lenta
	 *******************************************/
	for(i = 0; i < n_procs; i++)
	{
		if(pfi->getTime(proc_max)< pfi->getTime(i))
			proc_max = i;
	}

	/**********************************************
	 * Encontra a comunicação mais lenta
	 **********************************************/
	for(i = 0; i < n_tasks; i++)
	{
		for(j = 0; j < n_procs; j++)
		{
			if(B->at(max_com_j)->at(max_com_i)< B->at(j)->at(i))
			{
				max_com_i = i;
				max_com_j = j;
			}
		}
	}
	/***********************************************
	 *  e o maior processo para se transferir
	 ***********************************************/
	for(i = 0; i < n_tasks; i++)
	{
		if(s->vertices.at(max_mem).getpupSize() < s->vertices.at(i).getpupSize())
		{
			max_mem = i;
		}
	}
	/***********************************************
	 * Calcula o current_max_step_time(pior caso)
	 ***********************************************/
	pf = pfi->getTime(proc_max) + migcost;
	if (_lb_args.debug() > 4)
		CkPrintf("O maior tempo é compload %lf comm %d - %lf\n",
				s->vertices.at(max_task_time).getCompLoad(),
				B->at(max_com_j)->at(max_com_i),
				pf);
	return pf;
}

ProcFullInfo::ProcFullInfo(BaseLB::LDStats *s)
{
	int i;
	ProcArray *p = new ProcArray(s);
	Pfv = new vector<procfullinfo>();
	procfullinfo pi;

	for(i = 0; i < p->procs.size();i++)
	{
		Pfv->push_back(pi);
		Pfv->at(i).id = i;
		Pfv->at(i).speed = s->procs[i].pe_speed;
		Pfv->at(i).wall = s->procs[i].total_walltime;
		Pfv->at(i).time = s->procs[i].total_walltime - s->procs[i].idletime;
		Pfv->at(i).idle = s->procs[i].idletime;
		Pfv->at(i).load = (s->procs[i].total_walltime - s->procs[i].idletime)/s->procs[i].total_walltime;
	}
	delete p;
};

#include "MigBSPBLB.def.h"
/*@}*/
